﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApiSecurity.Api
{
    public static class Step05
    {
        public static void Register(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new AuthenticationHandler());

            config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
        }

        private class AuthenticationHandler : DelegatingHandler
        {
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                if (!request.Headers.Date.HasValue || Math.Abs(request.Headers.Date.Value.Subtract(DateTimeOffset.Now).TotalMinutes) > 5)
                    return new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Request Date missing or too far in the past/future")).Response;

                var accessKey = request.Headers.Where(x => x.Key == "Demo-Key").SelectMany(x => x.Value).FirstOrDefault();

                if (string.IsNullOrWhiteSpace(accessKey) || !Repository.Keys.ContainsKey(accessKey))
                    return new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid App Access Key")).Response;

                var signature = request.Headers.Where(x => x.Key == "Demo-Signature").SelectMany(x => x.Value).FirstOrDefault();

                if (signature != ComputeSignature(request, accessKey, Repository.Keys[accessKey]))
                    return new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid Signature")).Response;

                return await base.SendAsync(request, cancellationToken);
            }

            private string ComputeSignature(HttpRequestMessage request, string accessKey, string accessKeySecret)
            {
                var sb = new StringBuilder();

                sb.AppendLine(accessKey);
                sb.AppendLine(request.Method.ToString());
                sb.AppendLine(request.RequestUri.AbsolutePath);
                sb.AppendLine(request.Headers.Date.GetValueOrDefault().ToString("r"));

                // Some people also like to add the request body here (or some of it)

                using (var hmac = new HMACSHA256(Convert.FromBase64String(accessKeySecret)))
                    return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
            }
        }

        private static class Repository
        {
            public static readonly IDictionary<string, string> Keys = new Dictionary<string, string> {{"920ab4e7-27cf-4236-99b2-7538ce11c28e", "K+bGN6OgfGN4O6Wrj8SRJK//PO4I1QsQNlFHC5pYvsE="}};
        }
    }
}
